#include <strmif.h>
#include "mkvparserstream.hpp"
#include "mkvparser.hpp"
#include <cassert>
#include <sstream>
#include <iomanip>
#include <vfwmsgs.h>
#ifdef _DEBUG
#include "odbgstream.hpp"
using std::endl;
#endif

namespace MkvParser
{


Stream::Stream(Track* pTrack) :
    m_pTrack(pTrack)
{
    Init();
}


Stream::~Stream()
{
}


void Stream::Init()
{
    m_pBase = 0;
    m_pCurr = 0;  //lazy init this later
    m_pStop = m_pTrack->GetEOS();  //menas play entire stream
    //m_baseTime = 0;  //all streams must always use same base time
    m_bDiscontinuity = true;
}


std::wstring Stream::GetId() const
{
    std::wostringstream os;
    
    GetKind(os);  //"Video" or "Audio"
    
    os << std::setw(3) << std::setfill(L'0') << m_pTrack->GetNumber();
    
    return os.str();
}        


std::wstring Stream::GetName() const
{    
    const Track* const t = m_pTrack;
    assert(t);
    
    if (const wchar_t* codecName = t->GetCodecName())
        return codecName;
        
    if (const wchar_t* name = t->GetName())
        return name;
        
    if (ULONG tn = t->GetNumber())
    {
        std::wostringstream os;
        os << L"Track" << tn;
        return os.str();
    }
    
    if (const char* codecId = t->GetCodecId())
    {
        std::wstring result;
        
        const char* p = codecId;
 
        while (*p)
            result += wchar_t(*p++);  //TODO: is this cast meaningful?
            
        return result;
    }
        
    return GetId();
}


__int64 Stream::GetDuration() const
{
    Segment* const pSegment = m_pTrack->m_pSegment;
    assert(pSegment);
    
    const __int64 ns = pSegment->GetDuration();  //scaled (nanoseconds)
    assert(ns >= 0);
    
    const __int64 d = ns / 100;  //100-ns ticks
    
    return d;
}


HRESULT Stream::GetAvailable(LONGLONG* pLatest) const
{
    if (pLatest == 0)
        return E_POINTER;
        
    LONGLONG& pos = *pLatest;  //units are current time format
        
    const Segment* const pSegment = m_pTrack->m_pSegment;
    
    if (pSegment->Unparsed() <= 0)
        pos = GetDuration();
    else
    {    
        const Cluster* const pCluster = pSegment->GetLast();
    
        if ((pCluster == 0) || pCluster->EOS())
            pos = 0;
        else
        {
            const __int64 ns = pCluster->GetTime();
            pos = ns / 100;  //TODO: reftime units are assumed here
        }
    }
        
    return S_OK;
}


__int64 Stream::GetCurrPosition() const
{
    return GetCurrTime();  //TODO: for now only support reftime units
}


__int64 Stream::GetCurrTime() const
{
    if (m_pCurr == 0)  //NULL means lazy init hasn't happened yet
        return 0;      //TODO: assumes track starts with t=0
        
    if (m_pCurr->EOS())
        return GetDuration();  //TODO: can we do better than this?
        
    const __int64 ns = m_pCurr->GetTime();
    
    const __int64 reftime = ns / 100;  //100-ns ticks
    
    return reftime;
}


__int64 Stream::GetStopPosition() const
{
    return GetStopTime();  //TODO: for now we only support reftime units
}


__int64 Stream::GetStopTime() const
{
    if ((m_pStop == 0) || m_pStop->EOS())
        return GetDuration();  //TODO: can we do better than this?
        
    const __int64 ns = m_pStop->GetTime();
    
    const __int64 reftime = ns / 100;  //100-ns ticks
    
    return reftime;
}


#if 0
void Stream::LoadCurrPosition(
    LONGLONG pos_reftime, 
    DWORD dwCurr_, 
    __int64& parse_result)
{
    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    assert(dwCurrPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    //TODO: note that we load the curr posn (during a seek) by
    //sequentially load clusters, in order to determine their
    //location and size.  We only load the blocks within a cluster
    //when we actually need them -- so this is faster than paging
    //in all of the blocks in order to reach the cluster containing
    //the seek point, but it still means we must scan the file to
    //locate clusters.  It would be better if we could find the
    //clusters faster than via a mere sequential scan.  We might
    //be able to do that by using the SeekHead.  One approach would
    //be to find the corresponding keyframe in the SeekHead,
    //and the locate the containing cluster from that.  But that
    //might mean we would have to load clusters non-sequentially,
    //which we currently do not support.  (Technically we're within
    //the letter of the law using our existing approach.  We do say
    //in GetAvailable which seek positions are fast, and it's true
    //that we only need to peform a sequential scan when we attempt
    //to seek beyond which clusters have been loaded -- so if that's
    //too slow then the user shouldn't try to do that.)
    //
    //UPDATE: this algorithm doesn't work.  The problem is that
    //loading clusters takes a long time if the seek point is much
    //larger that what have currently loaded.  The worker thread
    //attempts to call PopulateSample, but the acquisition of the 
    //filter lock times out, because clusters are being loaded.
    
    parse_result = 0;  //tentative success
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);
    
    const __int64 pos_ns = pos_reftime * 100;
    __int64 tCurr_ns;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:  //applies only to stop pos
        default:
            assert(false);
            return;  

        case AM_SEEKING_AbsolutePositioning:
        {
            tCurr_ns = pos_ns;
            
            if (tCurr_ns < duration_ns)
                parse_result = pSegment->Load(tCurr_ns);

            break;
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pCurr)
            {
                tCurr_ns = m_pCurr->GetTime() + pos_ns;
                
                if (tCurr_ns < duration_ns)
                    parse_result = pSegment->Load(tCurr_ns);
            }
            else if (pos_ns < 0)
            {
                tCurr_ns = duration_ns + pos_ns;
                parse_result = pSegment->Load(tCurr_ns);
            }
                
            break;
        }      
    }
}
#endif


#if 0
void Stream::SetCurrPosition(
    LONGLONG pos_reftime, 
    DWORD dwCurr_)
{
    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    assert(dwCurrPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);
    
    const __int64 pos_ns = pos_reftime * 100;
    __int64 tCurr_ns;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:  //applies only to stop pos
        default:
            assert(false);
            return;  

        case AM_SEEKING_AbsolutePositioning:
        {
            tCurr_ns = pos_ns;
            break;
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pCurr == 0)
                tCurr_ns = pos_ns;  //t=0 is assumed here
            else if (m_pCurr->EOS())
                tCurr_ns = duration_ns + pos_ns;
            else
                tCurr_ns = m_pCurr->GetTime() + pos_ns;
                
            break;
        }      
    }
    
    if (tCurr_ns <= 0)
    {
        m_pCurr = 0;  //lazy init later
        m_baseTime = 0;
    }
    else if (tCurr_ns >= duration_ns)
    {
        m_pCurr = m_pTrack->GetEOS();
        //m_baseTime = 0;  (doesn't matter)
    }
    else
    {                
        //We are assuming that all streams arrive at this same value
        //for the time.  They all must have the same base time,
        //and the best we can do is to use the time of the cluster
        //containing time t as the base time (for all streams).
        
        const Cluster* const pCluster = pSegment->GetCluster(tCurr_ns);
        
        if (pCluster == 0)  //no clusters are loaded yet
        {
            m_pCurr = 0;   //lazy init later
            m_baseTime = 0;
        }
        else
        {        
            m_pCurr = pCluster->GetBlock(m_pTrack);
            
            if (m_pCurr == 0)
            {
#ifdef _DEBUG
                odbgstream os;
                os << "\nStream::SetCurrPosn: pCurr=NULL; pTrack->num="
                   << m_pTrack->GetNumber()
                   << " tCurr_ns="
                   << tCurr_ns
                   << " pCluster[t=" << tCurr_ns << "]->time=" << pCluster->GetTime()
                   << endl;
#endif

                //TODO: we should handle the NULL case better.  GetBlock
                //returns NULL, we should search the later clusters
                //for a block with a matching track and time.
                //
                //The effect of setting pCurr to EOS is that this stream
                //will effectively stop playing.  This is the same behavior
                //as if we had simply played to the point, and found no blocks
                //for this track in the cluster.
            
                m_pCurr = m_pTrack->GetEOS();  //TODO
            }

            //The base time is THE most important value in a multi-stream 
            //seek.  All streams must use the exact same base time, otherwise 
            //the times across streams will be out of sync.  It doesn't matter
            //whether the curr time is different across streams (all that means
            //is that streams begin rendering at slightly different times).
            
            m_baseTime = pCluster->GetTime();
        }
    }

    m_bDiscontinuity = true;
}
#else
const Cluster* Stream::SetCurrPosition(
    LONGLONG currpos_reftime, 
    DWORD dwCurr_)
{
    const DWORD dwCurrPos = dwCurr_ & AM_SEEKING_PositioningBitsMask;
    assert(dwCurrPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);
    
    const __int64 currpos_ns = currpos_reftime * 100;
    __int64 tCurr_ns;
    
    switch (dwCurrPos)
    {
        case AM_SEEKING_IncrementalPositioning:  //applies only to stop pos
        default:
            assert(false);
            return 0;  

        case AM_SEEKING_AbsolutePositioning:
        {
            tCurr_ns = currpos_ns;
            break;
        }
        case AM_SEEKING_RelativePositioning:
        {
            if (m_pCurr == 0)
                tCurr_ns = currpos_ns;  //t=0 is assumed here
            else if (m_pCurr->EOS())
                tCurr_ns = duration_ns + currpos_ns;
            else
                tCurr_ns = m_pCurr->GetTime() + currpos_ns;
                
            break;
        }      
    }
    
    const Cluster* pBase;
        
    if (pSegment->GetCount() == 0)
    {
        if (pSegment->Unparsed() <= 0)
        {
            pBase = &pSegment->m_eos;
            m_pCurr = m_pTrack->GetEOS();
        }
        else
        {
            pBase = 0;
            m_pCurr = 0;  //lazy init later when we have data
        }
    }
    else if (tCurr_ns <= 0)
    {
        pBase = 0;
        m_pCurr = 0;  //lazy init later
    }
    else if (tCurr_ns >= duration_ns)
    {
        pBase = &pSegment->m_eos;
        m_pCurr = m_pTrack->GetEOS();
    }
    else
    {                
        pBase = pSegment->GetCluster(tCurr_ns);
        assert(pBase);
        assert(!pBase->EOS());
        
        m_pCurr = pBase->GetBlock(m_pTrack);
    }
    
    m_pBase = pBase;
    m_bDiscontinuity = true;
    
    return pBase;
}


void Stream::SetCurrPosition(const Cluster* pBase)
{
    if (pBase == 0)
        m_pCurr = 0;  //lazy init
    else
        m_pCurr = pBase->GetBlock(m_pTrack);            

    m_pBase = pBase;
    m_bDiscontinuity = true;
}
#endif


#if 0
void Stream::SetStopPosition(
    LONGLONG pos_reftime, 
    DWORD dwStop_)
{
    const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
    assert(dwStopPos != AM_SEEKING_NoPositioning);  //handled by caller

    //TODO: verify case when pCurr=NULL    

    if ((m_pCurr != 0) && m_pCurr->EOS())
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }
            
    const __int64 tCurr_ns = m_pCurr ? m_pCurr->GetTime() : 0;  //nanoseconds
    assert(tCurr_ns >= 0);

    const Cluster* const pCurrCluster = m_pCurr->m_pGroup->m_pCluster;
    pCurrCluster;
    assert(pCurrCluster);
    assert(tCurr_ns >= pCurrCluster->GetTime());
        
    Segment* const pSegment = m_pTrack->m_pSegment;    

    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);

    const __int64 pos_ns = pos_reftime * 100;        
    __int64 tStop_ns;
            
    switch (dwStopPos)
    {
        default:
            assert(false);
            return;

        case AM_SEEKING_AbsolutePositioning:
        {
            tStop_ns = pos_reftime;
            break;            
        }
        case AM_SEEKING_RelativePositioning:
        {
            if ((m_pStop == 0) || m_pStop->EOS())
                tStop_ns = duration_ns + pos_ns;
            else
                tStop_ns = m_pStop->GetTime() + pos_ns;
                                
            break;
        }      
        case AM_SEEKING_IncrementalPositioning:
        {
            if (pos_reftime <= 0)
            {
                m_pStop = m_pCurr;
                return;
            }
            
            tStop_ns = tCurr_ns + pos_ns;
            break;            
        }
    }
    
    if (tStop_ns <= tCurr_ns)
    {
        m_pStop = m_pCurr;
        return;
    }
    
    if (tStop_ns >= duration_ns)
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }
                
    const Cluster* pStopCluster = pSegment->GetCluster(tStop_ns);
    
    if (pStopCluster == 0)
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }
    
    if (pStopCluster == pCurrCluster)
    {
        pStopCluster = pSegment->GetNext(pStopCluster);
        
        if (pStopCluster == 0)
        {
            m_pStop = m_pTrack->GetEOS();
            return;
        }
    }
    
    //TODO: note that a NULL return from GetBlock indicates some kind of 
    //problem, either no audio in this cluster (for this audio track),
    //or no keyframe (for this video track).  A NULL value for pStop is
    //interpreted to mean "play to end of file", but this isn't necessarily
    //what we want.  We would want to keep searching clusters for a 
    //"good" block, so this really should be a loop, but we don't want
    //to search that hard, because we prefer to defer actual loads as long
    //as possible.  So under the assumption that this file has some
    //pathological format, we respond to this pathology by playing to the end.
    //(We define as "pathological" any cluster that doesn't have at least
    //one block of each track number, or doesn't have at least one keyframe.)
    
    m_pStop = pStopCluster->GetBlock(m_pTrack);
    assert((m_pStop == 0) || 
           m_pStop->EOS() ||
           (m_pStop->GetTime() >= tCurr_ns));
}
#else
void Stream::SetStopPosition(
    LONGLONG stoppos_reftime, 
    DWORD dwStop_)
{
    const DWORD dwStopPos = dwStop_ & AM_SEEKING_PositioningBitsMask;
    assert(dwStopPos != AM_SEEKING_NoPositioning);  //handled by caller
    
    Segment* const pSegment = m_pTrack->m_pSegment;
    
    if (pSegment->GetCount() == 0)
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }
    
    if ((m_pCurr != 0) && m_pCurr->EOS())
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }

    const __int64 tCurr_ns = m_pCurr ? m_pCurr->GetTime() : 0;  //nanoseconds
    assert(tCurr_ns >= 0);

    const Cluster* const pCurrCluster = m_pBase ? m_pBase : pSegment->GetFirst();
    pCurrCluster;
    assert(pCurrCluster);
    assert(!pCurrCluster->EOS());
    assert(tCurr_ns >= pCurrCluster->GetTime());
        
    const __int64 duration_ns = pSegment->GetDuration();
    assert(duration_ns >= 0);

    const __int64 stoppos_ns = stoppos_reftime * 100;        
    __int64 tStop_ns;
            
    switch (dwStopPos)
    {
        default:
            assert(false);
            return;

        case AM_SEEKING_AbsolutePositioning:
        {
            tStop_ns = stoppos_reftime;
            break;            
        }
        case AM_SEEKING_RelativePositioning:
        {
            if ((m_pStop == 0) || m_pStop->EOS())
                tStop_ns = duration_ns + stoppos_ns;
            else
                tStop_ns = m_pStop->GetTime() + stoppos_ns;
                                
            break;
        }      
        case AM_SEEKING_IncrementalPositioning:
        {
            if (stoppos_reftime <= 0)
            {
                m_pStop = m_pCurr;
                return;
            }
            
            tStop_ns = tCurr_ns + stoppos_ns;
            break;            
        }
    }
    
    if (tStop_ns <= tCurr_ns)
    {
        m_pStop = m_pCurr;
        return;
    }
    
    if (tStop_ns >= duration_ns)
    {
        m_pStop = m_pTrack->GetEOS();
        return;
    }
                
    const Cluster* pStopCluster = pSegment->GetCluster(tStop_ns);
    assert(pStopCluster);
    
    if (pStopCluster == pCurrCluster)
        pStopCluster = pSegment->GetNext(pStopCluster);
        
    m_pStop = pStopCluster->GetBlock(m_pTrack);
    assert((m_pStop == 0) || 
           m_pStop->EOS() ||
           (m_pStop->GetTime() >= tCurr_ns));
}
#endif


void Stream::SetStopPositionEOS()
{
    m_pStop = m_pTrack->GetEOS();
}


HRESULT Stream::PopulateSample(IMediaSample* pSample)
{
    if (pSample == 0)
        return E_INVALIDARG;
 
    //TODO: here's the problem we have.  When we parse just the 
    //headers, we haven't (pre)loaded any clusters.  Based on the
    //headers, we create streams (and outpins).  However, the
    //stream object is initialized based on the current state of 
    //the file object.  Since we haven't loaded any clusters yet,
    //Track::GetFirst returnes NULL and so pCurr=pStop, so when
    //we start EOS is delivered immediately.
    //
    //We have a few options.
    //
    //(1) When we init (Inpin::ReceiveConnection),
    //we could load the first cluster too, instead of just the headers.
    //However, that would potentially delay the initialization time
    //(since we must wait for the entire cluster to be loaded).
    //
    //(2) Give pCurr a value that means "we haven't loaded this block
    //yet."  We could use NULL for that purpose, but then we'd have to
    //synthesize a block whose value means "this block indicates that
    //you have reached end-of-stream." 
    //
    //Giving pStop a special value is something we might have to do
    //anyway, to handle seek issues.  If the user specifies a stop posn,
    //and this is an incremental load, then there's no guarantee that
    //the block corresponding to that stop posn has been loaded yet.
    //(The handling of curr vs. stop is different when seeking -- if
    //the curr time isn't loaded there's nothing else we can do except
    //adjust the requested time.  But stop is different, because it's
    //harmless if the stop time hasn't been loaded yet.  The only thorn
    //here is if the client requests that the media time corresponding to
    //that stop be returned -- there's no way for us to calculate it
    //exactly, because the block isn't loaded yet.)
    //
    //So if we do something special for stop time (e.g. instead of 
    //using a block ptr, we use a time), that would allow use to 
    //use the NULL block ptr to mean something special, such as 
    //"the curr block hasn't been loaded yet".  
    //
    //But then again, we must still return a block ptr from Track::GetNext,
    //so whatever that ptr value is, it must be different from the
    //nonce value we use to indicate "block not loaded yet".  So this
    //sort of forces us to synthesize an actual non-NULL ptr value
    //to indicate end-of-stream, if we want to reserver ptr value NULL
    //to mean "block not loaded".
    //
    //Another idea: if we have reached the stop posn, handle video
    //by continuing to send frames until we reach a keyframe.
    
    //odbgstream os;
        
    if (m_pCurr == 0)  //
    {
        const HRESULT hr = m_pTrack->GetFirst(m_pCurr);
        
        if (hr == VFW_E_BUFFER_UNDERFLOW)
        {
            //os << "stream::PopulateSample: GetFirst underflow" << endl;
            return hr;  //try again later
        }
            
        assert(SUCCEEDED(hr));
        assert(m_pCurr);
        
        m_pBase = m_pTrack->m_pSegment->GetFirst();
        assert(m_pBase);
    }

    if (m_pStop == 0)  //TODO: this test might not be req'd
    {
        if (m_pCurr->EOS())
        {
            //os << "stream::PopulateSample: curr->EOS" << endl;
            return S_FALSE;  //send EOS downstream
        }
    }
    else if (m_pCurr == m_pStop)
    {
        //os << "stream::PopulateSample: curr=stop" << endl;
        return S_FALSE;  //EOS
    }

    const Block* pNext;
    
    HRESULT hr = m_pTrack->GetNext(m_pCurr, pNext);
    
    if (hr == VFW_E_BUFFER_UNDERFLOW)
    {
        //os << "stream::PopulateSample: GetNext underflow" << endl;
        return hr;
    }
        
    assert(SUCCEEDED(hr));
    assert(pNext);
    
    //os << "stream::PopulateSample: curr=" 
    //   << (m_pCurr->EOS() ? -1 : m_pCurr->GetTime())
    //   << endl;

    hr = OnPopulateSample(pNext, pSample);
    assert(SUCCEEDED(hr));
    
    m_bDiscontinuity = false;    
    m_pCurr = pNext;
    
    return S_OK;
}


ULONG Stream::GetClusterCount() const
{
    return m_pTrack->m_pSegment->GetCount();
}


}  //end namespace MkvParser
